# -*- coding:utf-8 -*-
# DocBook XSL
# http://docbook.sourceforge.net/
# lxml
# http://lxml.de/
# To chm, download HTML Help Workshop
# http://www.microsoft.com/download/en/details.aspx?displaylang=en&id=21138
# To PDF, download fop
# http://xmlgraphics.apache.org/fop/
# Another to PDF, xep 
# http://www.renderx.com/
# xsltproc
# http://xmlsoft.org/

# version
VERSION="1.2.2"
# config : absolute path
DOCBOOK_BASEDIR=""
HHC_CMD=""
FOP_CMD=""
XEP_CMD="C:\\Program Files\\RenderX\\XEP\\xep.bat"
XSLTPROC_CMD=""

# options
# True: fop ,False: xep
USE_FOP=False
# True: xsltproc False:lxml
USE_XSLT=True

# source code begin:
import getopt
import sys
import os.path
# xslt with lxml
import lxml.etree
# xml parse
import xml.parsers.expat
import zipfile
import shutil
import glob
import subprocess 
import locale
import tempfile 

# verbose off
verbose=False
# stylesheet file
stylesheet_file=""

# docbook xsl directory
WORKING_PATH	=os.getcwd()
EXEC_PATH		=os.path.dirname(sys.argv[0])
# xslt engine and converter
if len(DOCBOOK_BASEDIR)==0:
	DOCBOOK_BASEDIR =os.path.join(EXEC_PATH,"docbook","docbook-xsl-ns-1.76.1")
if len(HHC_CMD)==0:
	HHC_CMD =os.path.join(EXEC_PATH,"docbook","htmlhelp","hhc.exe")
if len(FOP_CMD)==0:
	FOP_CMD =os.path.join(EXEC_PATH,"docbook","fop-1.0","fop.cmd")
if len(XEP_CMD)==0:
	XEP_CMD =os.path.join(EXEC_PATH,"docbook","xep","xep.bat")	
if len(XSLTPROC_CMD)==0:
	XSLTPROC_CMD =os.path.join(EXEC_PATH,"docbook","xsltproc","xsltproc.exe")	
	
# default FOP configure
FOP_CONFIG="""<?xml version="1.0"?>
<fop version="1.0">
<renderers>
	<renderer mime="application/pdf">
		<fonts>
			<auto-detect />
		</fonts>
	</renderer>
</renderers>
</fop>
"""
# XEP font configure, add them into xep.xml
XEP_CONFIG="""
<?xml version="1.0" encoding="utf-8"?>
<xi:include href="{:s}" xmlns:xi="http://www.w3.org/2003/XInclude"/>
<config xmlns="http://www.renderx.com/XEP/config">
	<font-group xml:base="file:/C:/Windows/Fonts/" label="Windows TrueType" embed="true" subset="true"> 
		<font-family name="SimSun">
			<font><font-data ttf="simsun.ttc"/></font>
		</font-family>      
		<font-family name="SimHei">
			<font><font-data ttf="simhei.ttf"/></font>
		</font-family>         
		<font-family name="MicrosoftYaHei">
			<font><font-data ttf="msyh.ttf"/></font>
			<font style="oblique"><font-data ttf="msyh.ttf"/></font>
			<font weight="bold"><font-data ttf="msyhbd.ttf"/></font>
			<font weight="bold" style="oblique"><font-data ttf="msyhbd.ttf"/></font>
		</font-family>  
		<font-family name="Consolas">
			<font><font-data ttf="consola.ttf"/></font>
			<font style="oblique"><font-data ttf="consolai.ttf"/></font>
			<font weight="bold"><font-data ttf="consolab.ttf"/></font>
			<font weight="bold" style="oblique"><font-data ttf="consolaz.ttf"/></font>      
		</font-family>  	
	</font-group>
</config>
"""

# current locale		
lang=locale.getdefaultlocale()[0]

locale_xsl={
"af":"",
"am":"",
"ar":"",
"as":"",
"ast":"",
"az":"",
"bg":"",
"bn":"",
"bn_in":"",
"bs":"",
"ca":"",
"cs":"",
"cy":"",
"da":"",
"de":"",
"el":"",
"en":"",
"eo":"",
"es":"",
"et":"",
"eu":"",
"fa":"",
"fi":"",
"fr":"",
"ga":"",
"gl":"",
"gu":"",
"he":"",
"hi":"",
"hr":"",
"hu":"",
"id":"",
"is":"",
"it":"",
"ja":"",
"ka":"",
"kn":"",
"ko":"",
"ky":"",
"la":"",
"lt":"",
"lv":"",
"ml":"",
"mn":"",
"mr":"",
"nb":"",
"nds":"",
"nl":"",
"nn":"",
"or":"",
"pa":"",
"pl":"",
"pt":"",
"pt_br":"",
"ro":"",
"ru":"",
"sk":"",
"sl":"",
"sq":"",
"sr":"",
"sr_latn":"",
"sv":"",
"ta":"",
"te":"",
"th":"",
"tl":"",
"tr":"",
"uk":"",
"vi":"",
"xh":"",
"zh":"""
<xsl:param name="l10n.gentext.default.language">zh_cn</xsl:param>
<xsl:param name="body.font.master">9</xsl:param>
<xsl:param name="body.font.family">Times New Roman,SimSun,MicrosoftYaHei,Serif</xsl:param>
<xsl:param name="title.font.family">Times New Roman,SimHei,MicrosoftYaHei,Sans Serif</xsl:param>
<xsl:param name="monospace.font.family">Consolas,SimSun,monospace</xsl:param>
<xsl:param name="symbol.font.family">Symbol,ZapfDingbats,Lucida Sans Unicode</xsl:param>
""",
"zh_cn":"""
<xsl:param name="l10n.gentext.default.language">zh_cn</xsl:param>
<xsl:param name="body.font.master">9</xsl:param>
<xsl:param name="body.font.family">Times New Roman,SimSun,MicrosoftYaHei,Serif</xsl:param>
<xsl:param name="title.font.family">Times New Roman,SimHei,MicrosoftYaHei,Sans Serif</xsl:param>
<xsl:param name="monospace.font.family">Consolas,SimSun,monospace</xsl:param>
<xsl:param name="symbol.font.family">Symbol,ZapfDingbats,Lucida Sans Unicode</xsl:param>
""",
"zh_tw":"""
<xsl:param name="l10n.gentext.default.language">zh_tw</xsl:param>
<xsl:param name="body.font.master">9</xsl:param>
<xsl:param name="body.font.family">Times New Roman,MingLiU,MicrosoftYaHei,Serif</xsl:param>
<xsl:param name="title.font.family">Times New Roman,MicrosoftYaHei,Sans Serif</xsl:param>
<xsl:param name="monospace.font.family">Consolas,MingLiU,monospace</xsl:param>
<xsl:param name="symbol.font.family">Symbol,ZapfDingbats,Lucida Sans Unicode</xsl:param>
"""
}

# xsl stylesheet
customized_xsl="""<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version='1.0'>
<!-- docbook xsl, must use 'import' here. use URI -->
<xsl:import href="{:s}"/>
<!-- default xsl -->
<xsl:param name="generate.toc">
article nop
book	toc,title,figure,table,example,equation
</xsl:param>
<!-- type xsl -->
{:s}
<!-- set locale and font -->
{:s}
<!-- cmdline xsl -->
{:s}
</xsl:stylesheet>"""

lxml_paramlist={}
xslt_param=""


# html transform
def CreateHtmlFile(transform,docbook_file,output_file):
	global xslt_param
	if USE_XSLT:
		xslt_param+=" -o "+output_file
		xslt_cmd=XSLTPROC_CMD+" "+xslt_param+" "+stylesheet_file+" "+docbook_file
		out=subprocess.PIPE
		if verbose:
			print(xslt_cmd)
			out=None
		retcode=subprocess.call(xslt_cmd,shell=True,stdout=out,stderr=out)	
	else:
		content=transform(lxml.etree.parse(docbook_file),**lxml_paramlist)
		if verbose:
			print(transform.error_log)
		f=open(output_file,"wb")
		f.write(content)
		f.close()
# epub transform	
def CreateEpubFile(transform,docbook_file,output_file):
	global xslt_param
	# epub files
	epub_files=["mimetype","META-INF","OEBPS"]
	if USE_XSLT:
		xslt_cmd=XSLTPROC_CMD+" "+xslt_param+" "+stylesheet_file+" "+docbook_file
		out=subprocess.PIPE
		if verbose:
			print(xslt_cmd)
			out=None
		retcode=subprocess.call(xslt_cmd,shell=True,stdout=out,stderr=out)
	else:	
		transform(lxml.etree.parse(docbook_file),**lxml_paramlist)
		if verbose:
			print(transform.error_log)	

	# check manifest file
	for file in GetManifestFiles(os.path.join(epub_files[2],"content.opf")):
		if not os.path.exists(os.path.join(epub_files[2],file)):
			if verbose:
				print("Copy file : ",file)
			shutil.copy(os.path.join(WORKING_PATH,file),epub_files[2])
	# create mimetype file
	f = open(epub_files[0],'w')
	f.write("application/epub+zip")
	f.close()
	# archive 
	epub = zipfile.ZipFile(output_file, 'w')
	epub.write(epub_files[0], compress_type=zipfile.ZIP_STORED)
	ZipDirectory(epub,epub_files[1])
	ZipDirectory(epub,epub_files[2])
	epub.close()
	os.remove(epub_files[0])
	shutil.rmtree(epub_files[1])
	shutil.rmtree(epub_files[2])
# htmlhelp transform
def CreateCHMFile(transform,docbook_file,output_file):
	global xslt_param
	if not os.path.exists(HHC_CMD):
		print("Can't find 'hhc'. Exit!")
		sys.exit()
	tempfile.tempdir=os.path.dirname(docbook_file)
	tempdir=tempfile.mkdtemp()
	os.chdir(tempdir)
	# 'img src path' must be ../ for using temprary directory 	
	if USE_XSLT:
		xslt_param+=" --stringparam imgsrc ../ --stringparam chmfile "+output_file
		xslt_cmd=XSLTPROC_CMD+" "+xslt_param+" "+stylesheet_file+" "+docbook_file
		out=subprocess.PIPE
		if verbose:
			print(xslt_cmd)
			out=None
		retcode=subprocess.call(xslt_cmd,shell=True,stdout=out,stderr=out)
	else:
		lxml_paramlist["chmfile"]="'{:s}'".format(output_file)
		lxml_paramlist["imgsrc"]="'{:s}'".format("../")
		transform(lxml.etree.parse(docbook_file),**lxml_paramlist)
		if verbose:
			print(transform.error_log)	
	hhc=HHC_CMD+" htmlhelp.hhp"
	out=subprocess.PIPE
	if verbose:
		print(hhc)
		out=None
	retcode=subprocess.call(hhc,shell=True,stdout=out,stderr=out)
	os.chdir(WORKING_PATH)	
	shutil.rmtree(tempdir)

# fo transform
def CreateFoFile(transform,docbook_file,output_file):
	global xslt_param
	if USE_XSLT :
		xslt_param+=" -o "+output_file
		xslt_cmd=XSLTPROC_CMD+" "+xslt_param+" "+stylesheet_file+" "+docbook_file
		out=subprocess.PIPE
		if verbose:
			print(xslt_cmd)
			out=None
		retcode=subprocess.call(xslt_cmd,shell=True,stdout=out,stderr=out)	
	else:
		content=transform(lxml.etree.parse(docbook_file),**lxml_paramlist)
		if verbose:
			print(transform.error_log)	
		f=open(output_file,"wb")
		f.write(content)
		f.close()
# pdf transform
def CreatePDFFile(transform,docbook_file,output_file):
	# create fo file
	foname = docbook_file + ".fo"
	CreateFoFile(transform,docbook_file,foname)

	if USE_FOP:
		if os.path.exists(FOP_CMD):
			FopConverter("-fo "+foname,"-pdf "+output_file)
		else:
			print("Can't find fop. Exit!")
			sys.exit()
	else:
		if os.path.exists(XEP_CMD):
			XepConverter("-fo "+foname,"-pdf "+output_file)	
		else:
			print("Can't find xep. Exit! :",XEP_CMD)
			sys.exit()
	os.remove(foname)
# rtf transform
def CreateRTFFile(transform,docbook_file,output_file):
	# create fo file
	foname = docbook_file + ".fo"
	CreateFoFile(transform,docbook_file,foname)

	if USE_FOP:
		if os.path.exists(FOP_CMD):
			FopConverter("-fo "+foname,"-rtf "+output_file)
		else:
			print("Can't find fop. Exit!")
			sys.exit()
	else:
		print("Xep can't support RTF file format. Please use FOP!")
		sys.exit()
	os.remove(foname)
# png transform
def CreatePNGFile(transform,docbook_file,output_file):
	# create fo file
	foname = docbook_file + ".fo"
	CreateFoFile(transform,docbook_file,foname)
	if USE_FOP:
		if os.path.exists(FOP_CMD):
			FopConverter("-fo "+foname,"-png "+output_file)
		else:
			print("Can't find fop. Exit!")
			sys.exit()
	else:
		print("Xep can't support PNG file format. Please use FOP!")
		sys.exit()
	os.remove(foname)
	
DOCBOOK_XSL={
"html":(CreateHtmlFile,os.path.join(DOCBOOK_BASEDIR,"html","docbook.xsl"),
"""
<xsl:output method="html" encoding="utf-8" indent="no"/>
<xsl:param name="chunker.output.encoding">utf-8</xsl:param>
"""),
"chm":(CreateCHMFile,os.path.join(DOCBOOK_BASEDIR,"htmlhelp","htmlhelp.xsl"),
"""
<!-- hhc don't support encoding utf-8 -->
<xsl:param name="chunker.output.encoding">GBK</xsl:param>
<xsl:param name="htmlhelp.encoding">GBK</xsl:param>
<xsl:param name="img.src.path" select="$imgsrc" />
<xsl:param name="htmlhelp.chm" select="$chmfile" />
"""),
"epub":(CreateEpubFile,os.path.join(DOCBOOK_BASEDIR,"epub","docbook.xsl"),
"""
<xsl:param name="chunker.output.encoding">utf-8</xsl:param>
"""),
"fo":(CreateFoFile,os.path.join(DOCBOOK_BASEDIR,"fo","docbook.xsl"),
"""
<xsl:param name="paper.type">A4</xsl:param>
<xsl:param name="body.start.indent">0pt</xsl:param>
<xsl:attribute-set name="normal.para.spacing">
  <xsl:attribute name="text-indent">2em</xsl:attribute>
  <xsl:attribute name="space-before.optimum">1em</xsl:attribute>
  <xsl:attribute name="space-before.minimum">0.8em</xsl:attribute>
  <xsl:attribute name="space-before.maximum">1.2em</xsl:attribute>
</xsl:attribute-set>
"""),
"pdf":(CreatePDFFile,os.path.join(DOCBOOK_BASEDIR,"fo","docbook.xsl"),
"""
<xsl:param name="paper.type">A4</xsl:param>
<xsl:param name="body.start.indent">0pt</xsl:param>
<xsl:attribute-set name="normal.para.spacing">
  <xsl:attribute name="text-indent">2em</xsl:attribute>
  <xsl:attribute name="space-before.optimum">1em</xsl:attribute>
  <xsl:attribute name="space-before.minimum">0.8em</xsl:attribute>
  <xsl:attribute name="space-before.maximum">1.2em</xsl:attribute>
</xsl:attribute-set>
<xsl:attribute-set name="figure.properties">
  <xsl:attribute name="text-align">center</xsl:attribute>
</xsl:attribute-set>
"""),
"rtf":(CreateRTFFile,os.path.join(DOCBOOK_BASEDIR,"fo","docbook.xsl"),
"""
<xsl:param name="paper.type">A4</xsl:param>
<xsl:param name="page.orientation">portrait</xsl:param>
<xsl:param name="body.start.indent">0pt</xsl:param>
<xsl:attribute-set name="normal.para.spacing">
  <xsl:attribute name="text-indent">2em</xsl:attribute>
  <xsl:attribute name="space-before.optimum">1em</xsl:attribute>
  <xsl:attribute name="space-before.minimum">0.8em</xsl:attribute>
  <xsl:attribute name="space-before.maximum">1.2em</xsl:attribute>
</xsl:attribute-set>
<!-- 
<xsl:param name="default.image.width">160mm</xsl:param>
<xsl:param name="ignore.image.scaling">1</xsl:param>
<xsl:attribute-set name="figure.properties">
  <xsl:attribute name="text-align">center</xsl:attribute>
</xsl:attribute-set>
-->
<xsl:attribute-set name="figure.properties">
  <xsl:attribute name="text-align">center</xsl:attribute>
</xsl:attribute-set>
"""),
"png":(CreatePNGFile,os.path.join(DOCBOOK_BASEDIR,"fo","docbook.xsl"),
"""
<xsl:param name="paper.type">A4</xsl:param>
<xsl:param name="body.start.indent">0pt</xsl:param>
<xsl:attribute-set name="normal.para.spacing">
  <xsl:attribute name="text-indent">2em</xsl:attribute>
  <xsl:attribute name="space-before.optimum">1em</xsl:attribute>
  <xsl:attribute name="space-before.minimum">0.8em</xsl:attribute>
  <xsl:attribute name="space-before.maximum">1.2em</xsl:attribute>
</xsl:attribute-set>

""")

}

# functions
# compress directory
def ZipDirectory(zip,path):
	for v in os.listdir(path):
		v=os.path.join(path,v)
		if os.path.isdir(v):
			ZipDirectory(zip,v)
		else:
			if verbose:
				print("Add file : "+v)
			zip.write(v, compress_type=zipfile.ZIP_DEFLATED)
def FopConverter(input,output):
	# fix fop fonts
	# fop_conf=os.path.join(os.path.dirname(FOP_CMD),"conf","db2_fop.xconf")
	fop_conf=os.path.join(EXEC_PATH,"docbook","dbktool_fop.xconf")
	if not os.path.exists(fop_conf):
		f=open(fop_conf,"w")
		f.write(FOP_CONFIG)
		f.close()
		if verbose:
			print("Can't find {:s} file. Create it.".format(fop_conf))
	fop_cmd="\""+FOP_CMD+"\""+" -c "+fop_conf+" "+input+" "+output
	out=subprocess.PIPE
	if verbose:
		print(fop_cmd)
		out=None
	retcode=subprocess.call(fop_cmd,shell=True,stdout=out,stderr=out)	
def XepConverter(input,output):
	# fix xep fonts
	#~ xep_conf=os.path.join(EXEC_PATH,"docbook","dbktool_xep.xml")
	#~ if not os.path.exists(xep_conf):
		#~ f=open(xep_conf,"w")
		#~ f.write(XEP_CONFIG)
		#~ f.close()
		#~ if verbose:
			#~ print("Can't find {:s} file. Create it.".format(xep_conf))	
	xep_cmd="\""+XEP_CMD+"\""+" "+input+" "+output
	out=subprocess.PIPE
	if verbose:
		print(xep_cmd)
		out=None
	retcode=subprocess.call(xep_cmd,shell=True,stdout=out,stderr=out)		
		
def GetManifestFiles(xml_file):
	def StartElement(name,attrs):
		nonlocal Manifest
		nonlocal ManifestFiles
		if name == "manifest":
			Manifest = True
		if Manifest and name == "item":
			ManifestFiles.append(attrs['href'])
	def EndElement(name):
		nonlocal Manifest
		if name == "manifest":
			Manifest = False
	Manifest = False
	ManifestFiles=[]
	p = xml.parsers.expat.ParserCreate()
	p.StartElementHandler=StartElement
	p.EndElementHandler=EndElement
	p.ParseFile(open(xml_file,'rb'))
	return ManifestFiles
	

def ConvertDocbook(transform,docbook_file,type,output_file):
	if len(output_file) == 0:
		output_file = os.path.splitext(docbook_file)[0] + "." + type
	output_file=os.path.abspath(output_file)
	docbook_file=os.path.abspath(docbook_file)
	print("Input file  : "+docbook_file)
	DOCBOOK_XSL[type][0](transform,docbook_file,output_file)
	print("Output file : "+output_file)


def Hello():
	print("""
DBKTool Version {:s}
Convert DocBook(.dbk) to epub,html,chm,fo,pdf,rtf or png file.
""".format(VERSION))

def usage():
	print("""
default locale : {:s}

USAGE
dbktool [options] <docbook file>
[OPTIONS]
-h  --help         help message
-o  --output       output file
-x  --xsl          xsl file
-c  --converter    converter: fop, xep. XEP only support pdf file.
-e  --engine       xslt engine: xsltproc, lxml
-t  --type         type: epub,html,chm,fo,pdf,rtf,png
-l  --locale       language:en,zh_cn,zh_tw etc.
-p  --stringparam  pass a key and value pair to xslt
-v  --verbose      show verbose

[Examples]
db2 test.xml
db2 -p "l10n.gentext.language=zh_cn" -t epub test.xml
db2 -t chm -o a.chm test.xml
""".format(lang))
	
if __name__ == "__main__":
	Hello()
	
	if not os.path.exists(DOCBOOK_BASEDIR):
		print("Can't find docbook xsl. Exit!")
		sys.exit()
		
	try:
		opts,args=getopt.getopt(sys.argv[1:],"hvo:t:l:x:p:c:e:",
			["help","verbose","output=","type=","locale=","xsl==","stringparam=","converter","engine"])
	except getopt.GetoptError as err:
		print(err)
		usage()
		sys.exit(2)

	if len(args)==0 :
		usage()
		sys.exit(1)
	
	type="html"
	output=""
	cmdline_xsl_param=""
	xsl_file=""
	for o,v in opts:
		if o in ("-v","--verbose"):
			verbose= True
		elif o in ("-h","--help"):
			usage()
			sys.exit()
		elif o in ("-o","--output"):
			output=v
		elif o in ("-x","--xsl"):
			xsl_file=os.path.abspath(v)
		elif o in ("-c","--converter"):
			if v == "FOP" or v=="fop":
				USE_FOP=True
			else:
				USE_FOP=False
		elif o in ("-e","--engine"):
			if v == "xsltproc":
				USE_XSLT=True
			else:
				USE_XSLT=False				
		elif o in ("-t","--type"):
			type=v
		elif o in ("-l","--locale"):
			lang=v		
		elif o in ("-p","--stringparam"):
			k,p=v.replace(" ","").split("=")
			tempk="_{:s}".format(k.replace(".","_"))
			cmdline_xsl_param+="""<xsl:param name="{:s}" select="${:s}" />\n""".format(k,tempk)
			lxml_paramlist[tempk]="'{:s}'".format(p)
			xslt_param+=""" --stringparam {:s} {:s}""".format(tempk,p)
			
	if type not in DOCBOOK_XSL:
		usage()
		sys.exit(2)
	if len(xsl_file)!=0:
		cmdline_xsl_param+="""<xsl:include href="{:s}"/>""".format(xsl_file.replace("\\","/"))
	xsl_content=customized_xsl.format(DOCBOOK_XSL[type][1].replace("\\","/"),
		DOCBOOK_XSL[type][2],
		locale_xsl[lang.lower()],
		cmdline_xsl_param
		)
	tempdir=tempfile.mkdtemp()
	stylesheet_file=os.path.join(tempdir,"temp.xsl")
	# NOTE: character is UTF-8 encoding.
	f=open(stylesheet_file,"w")
	f.write(xsl_content)
	f.close()
	
	if USE_XSLT:
		transform=""
	else:
		xsl_etree=lxml.etree.parse(stylesheet_file)
		#xsl_etree=lxml.etree.XML(xsl)
		transform =lxml.etree.XSLT(xsl_etree,access_control=lxml.etree.XSLTAccessControl())
	
	if verbose:
		print("DocBook XSL : "+xsl_content)
	for v in args:
		# 通配符 *.xml
		filelist=glob.glob(v)
		if len(filelist)==0:
			print("Can't find file : "+v)
			continue
		for p in filelist:
			ConvertDocbook(transform,p,type,output)
	shutil.rmtree(tempdir)



